﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Data;
using System.Drawing;
using System.Drawing.Drawing2D;
using LightEngine.Mathematics;

namespace LightEngine
{
    namespace VisualTools
    {
        namespace WindowsFormsTools
        {

            class LinesGraphDrawer<GraphData> : GraphDrawer<GraphData>
            //where GraphData : ICloneable
            {
                private Graphics RealDrawer;
                private Boolean HorizontalAutoScaling = true;
                private UInt16 DrawOffsetIndex = 0;
                private Int32 LeftMarkerPosition = 0, RightMarkerPosition = 0;
                private Form ImageContainer = null;
                private DrawType LastHandled = DrawType.LeftMarkers;

                public LinesGraphDrawer() { }

                public void AssignUpdatingControl(Form Container) { ImageContainer = Container; }

                public LinesGraphDrawer(Graphics Drawer)
                {
                    RealDrawer = Drawer;
                }

                public void AssignDrawer(Graphics Drawer)
                {
                    RealDrawer = Drawer;
                }

                public void EnableHorizontalAutoAdjusting()
                {
                    HorizontalAutoScaling = true;
                }

                public void DisableHorizontalAutoAdjusting()
                {
                    HorizontalAutoScaling = false;
                }

                private void AdjustImagePosition()
                {
                    if (Points.Length < Positioning().GetCanvasContainerWidth())
                    {
                        // анализ соотношения размеров графика(числа точек) и ширины по умолчанию...
                        if (HorizontalAutoScaling == true)
                        {
                            Double Scale = (Double)(Int32)((Double)Positioning().GetRealWidth() / (Double)GraphSource.Length);
                            Positioning().SetXScaleCoefficient(Scale);
                            Double Space = (Double)(Positioning().GetCanvasContainerWidth() - Points.Length * Positioning().GetXScaleCoefficient()) / (Double)Positioning().GetCanvasContainerWidth();
                            Positioning().LeftSpaceLobe = Space / 2.0;
                            Positioning().RightSpaceLobe = Positioning().LeftSpaceLobe;
                        }
                        else
                        {
                            Double Space = (Double)(Positioning().GetCanvasContainerWidth() - Points.Length) / (Double)Positioning().GetCanvasContainerWidth();
                            Positioning().LeftSpaceLobe = Space / 2.0;
                            Positioning().RightSpaceLobe = Positioning().LeftSpaceLobe;
                        }
                    }
                    else
                    {
                        Positioning().SetXScaleCoefficient(1.0);
                    }
                }


                protected override Boolean CalculateCoordinates()
                {
                    if (GraphSource == null)
                        return false;
                    // Позиционирование изображения по центру
                    AdjustImagePosition();
                    DrawOffsetIndex = 0;
                    try
                    {
                        // 1. Стартовая точка - левый верхний угол с отступом
                        Double StartX = (Double)Positioning().GetCanvasContainerWidth() * Positioning().LeftSpaceLobe;
                        Double StartY = (Double)Positioning().GetCanvasContainerHeight() * Positioning().TopSpaceLobe;
                        LeftMarkerPosition = (Int32)StartX;
                        RightMarkerPosition = (Int32)(StartX + Math.Min(GraphSource.Length, Positioning().GetRealWidth()));
                        GraphData MinY = GraphSource.Min();
                        GraphData MaxY = GraphSource.Max();
                        String MinYStr = MinY.ToString();
                        String MaxYStr = MaxY.ToString();
                        Double MinimalValue = Double.Parse(MinYStr);
                        Double MaximalValue = Double.Parse(MaxYStr);
                        // Сначала используется автомасштабирование изображения, для того, чтобы полностью
                        // вписать его в холст.... в дальнейшем масштаб может быть изменен и холст в случае
                        // увеличения масштабирующего коэффициента будет отображать только часть изображения.
                        Double Step = (Double.Parse(MaxYStr) - Double.Parse(MinYStr)) / Positioning().GetRealHeight();
                        for (Int32 Counter = 0; Counter < GraphSource.Length; Counter++)
                        {
                            if (HorizontalAutoScaling == true) Points[Counter].X = (Int32)StartX + (Int32)((Double)Counter * Positioning().GetXScaleCoefficient());
                            else Points[Counter].X = (Int32)StartX + Counter;

                            if (GraphVerticalDrawingStyle == UpperPoints.UpDown)
                                Points[Counter].Y = (Int32)StartY + (Int32)((Double.Parse(GraphSource[Counter].ToString()) - MinimalValue) / Step);
                            else Points[Counter].Y = (Int32)StartY + (Int32)((MaximalValue - Double.Parse(GraphSource[Counter].ToString())) / Step);
                        }
                    }
                    catch (Exception AnyException)
                    {
                        AnyException.ToString();
                        return false;
                    }
                    return true;
                }

                protected override void DrawGraph()
                {
                    using (Pen PointsPen = new Pen(GetGraphAttribute(DrawType.Points).AttributeColor,
                                                   (float)GetGraphAttribute(DrawType.Points).AttribudeWidth))
                    {
                        if (GraphicPoints(true) == null)
                        {
                            // Рисование порциямb, несмасштабированного графика...
                            UInt16 EndPointIndex = ((UInt16)(DrawOffsetIndex + Positioning().GetRealWidth()) < (UInt16)Points.Length)
                                                   ? (UInt16)(DrawOffsetIndex + (UInt16)Positioning().GetRealWidth()) : (UInt16)(Points.Length - 1);
                            for (UInt16 Counter = (UInt16)(DrawOffsetIndex); Counter < EndPointIndex; Counter++)
                            {
                                RealDrawer.DrawLine(PointsPen,
                                                    Points[Counter - DrawOffsetIndex].X,
                                                    Points[Counter].Y,
                                                    Points[Counter - DrawOffsetIndex + 1].X,
                                                    Points[Counter + 1].Y);
                            }
                        }
                        else
                        {
                            //Рисование смасштабированной части графика...
                        }
                        DrawMarkers();
                        DrawAxis();
                    }
                }

                private void DrawMarkers()
                {
                    if (GetGraphAttribute(DrawType.LeftMarkers).AttributeEnabled == true)
                    {
                        using (Pen MarkersPen = new Pen(GetGraphAttribute(DrawType.LeftMarkers).AttributeColor,
                               (float)GetGraphAttribute(DrawType.LeftMarkers).AttribudeWidth))
                        {
                            Double Offset = Positioning().TopSpaceLobe * Positioning().GetCanvasContainerHeight();
                            RealDrawer.DrawLine(MarkersPen, LeftMarkerPosition, (float)Offset,
                                                LeftMarkerPosition, Positioning().GetRealHeight() + (float)Offset);
                        }
                    }

                    if (GetGraphAttribute(DrawType.RightMarkers).AttributeEnabled == true)
                    {
                        using (Pen MarkersPen = new Pen(GetGraphAttribute(DrawType.LeftMarkers).AttributeColor,
                                                        (float)GetGraphAttribute(DrawType.LeftMarkers).AttribudeWidth))
                        {
                            Double Offset = Positioning().TopSpaceLobe * Positioning().GetCanvasContainerHeight();
                            RealDrawer.DrawLine(MarkersPen, RightMarkerPosition, (float)Offset,
                                                RightMarkerPosition, Positioning().GetRealHeight() + (float)Offset);
                        }
                    }
                }

                private void DrawAxis()
                {
                    // Рисование подписей к осям
                    if (GetGraphAttribute(DrawType.XAxisTitle).AttributeEnabled == true)
                    {
                        using (SolidBrush XAxisBrush = new SolidBrush(GetGraphAttribute(DrawType.XAxisTitle).AttributeColor))
                        {
                            Double XPosition = (Double)Positioning().GetCanvasContainerWidth() / 2.0 -
                                               (Double)(GetGraphAttribute(DrawType.XAxisTitle).AttributeName.Length) * (GetGraphAttribute(DrawType.XAxisTitle).AttributeFont.Size) / 2.0;
                            Double YPosition = (Double)Positioning().GetCanvasContainerHeight() - (GetGraphAttribute(DrawType.XAxisTitle).AttributeFont.Size) * 2.0;
                            RealDrawer.DrawString(GetGraphAttribute(DrawType.XAxisTitle).AttributeName,
                                                  GetGraphAttribute(DrawType.XAxisTitle).AttributeFont,
                                                  XAxisBrush, (float)XPosition, (float)YPosition);
                        }
                    }

                    if (GetGraphAttribute(DrawType.YAxisTitle).AttributeEnabled == true)
                    {
                        using (SolidBrush YAxisBrush = new SolidBrush(GetGraphAttribute(DrawType.YAxisTitle).AttributeColor))
                        {
                            Double XPosition = GetGraphAttribute(DrawType.YAxisTitle).AttributeFont.Size * 2.0;
                            Double YPosition = ((Double)Positioning().GetCanvasContainerHeight() / 2.0 + (Double)(GetGraphAttribute(DrawType.YAxisTitle).AttributeName.Length * GetGraphAttribute(DrawType.YAxisTitle).AttributeFont.SizeInPoints)) / 1.50;
                            StringFormat DrawFormat = new StringFormat(); DrawFormat.FormatFlags = StringFormatFlags.DirectionVertical;

                            using (Matrix TransformMatrix = new Matrix())
                            {
                                TransformMatrix.RotateAt(180F, new PointF((Int32)XPosition, (Int32)YPosition));
                                RealDrawer.Transform = TransformMatrix;
                                RealDrawer.DrawString(GetGraphAttribute(DrawType.YAxisTitle).AttributeName,
                                                      GetGraphAttribute(DrawType.YAxisTitle).AttributeFont,
                                                      YAxisBrush,
                                                      (float)XPosition, (float)YPosition,
                                                      DrawFormat);
                                RealDrawer.ResetTransform();
                            }
                        }
                    }

                    // Рисование Осей ....
                    if (GetGraphAttribute(DrawType.Axis).AttributeEnabled == true)
                    {
                        float X1 = (float)(Positioning().LeftSpaceLobe * Positioning().GetCanvasContainerWidth() * 0.75),
                              Y1 = (float)(Positioning().GetCanvasContainerHeight() * Positioning().TopSpaceLobe * 0.75),
                              Y2 = (float)(Positioning().GetCanvasContainerHeight() * (1.0 - Positioning().BottomSpaceLobe * 0.75)),
                              X3 = (float)(Positioning().GetCanvasContainerWidth() * (1.0 - Positioning().RightSpaceLobe * 0.75));
                        using (Pen AxisPen = new Pen(GetGraphAttribute(DrawType.Axis).AttributeColor,
                                                     (float)GetGraphAttribute(DrawType.Axis).AttribudeWidth))
                        {
                            RealDrawer.DrawLine(AxisPen, X1, Y1, X1, Y2);
                            RealDrawer.DrawLine(AxisPen, X1, Y2, X3, Y2);
                            RealDrawer.DrawLine(AxisPen, X3, Y1, X3, Y2);
                            RealDrawer.DrawLine(AxisPen, X1, Y1, X3, Y1);
                        }

                        Double[] XMarks = new Double[GetGraphAttribute(DrawType.XAxisMarks).AttributeQuantity];
                        Double[] YMarks = new Double[GetGraphAttribute(DrawType.YAxisMarks).AttributeQuantity];

                        using (Pen XAxisMarksPen = new Pen(GetGraphAttribute(DrawType.XAxisMarks).AttributeColor,
                                                            (float)GetGraphAttribute(DrawType.XAxisMarks).AttribudeWidth))
                        {
                            for (Int32 Counter = 0; Counter < XMarks.Length; Counter++)
                            {
                                XMarks[Counter] = (Double)Points[0].X +
                                                  (Double)(Positioning().GetCanvasContainerWidth() * (Double)Counter / (Double)XMarks.Length);
                                if (XMarks[Counter] >= Points[0].X + Positioning().GetRealWidth())
                                    XMarks[Counter] = Points[0].X + Positioning().GetRealWidth() - 1;
                                RealDrawer.DrawLine(XAxisMarksPen, (float)XMarks[Counter], Y2, (float)XMarks[Counter], Y2 + GetGraphAttribute(DrawType.XAxisMarks).AttributeLength);
                            }
                        }
                        using (Pen YAxisMarksPen = new Pen(GetGraphAttribute(DrawType.YAxisMarks).AttributeColor,
                                                           (float)GetGraphAttribute(DrawType.YAxisMarks).AttribudeWidth))
                        {
                            for (Int32 Counter = 0; Counter < YMarks.Length; Counter++)
                            {
                                YMarks[Counter] = (Double)Positioning().GetCanvasContainerHeight() * Positioning().TopSpaceLobe +
                                                  (Double)Positioning().GetCanvasContainerHeight() * (Double)Counter / (Double)YMarks.Length;
                                RealDrawer.DrawLine(YAxisMarksPen, X1 - GetGraphAttribute(DrawType.YAxisMarks).AttributeLength, (float)YMarks[Counter], X1, (float)YMarks[Counter]);
                            }
                        }
                        if (GetGraphAttribute(DrawType.IntermediateLines).AttributeEnabled == true &&
                            GetGraphAttribute(DrawType.IntermediateLines).AttributeLength > 0)
                        {
                            using (Pen IntermediateLinesPen = new Pen(GetGraphAttribute(DrawType.IntermediateLines).AttributeColor,
                                                                      (float)GetGraphAttribute(DrawType.IntermediateLines).AttribudeWidth))
                            {
                                for (Int32 Counter = 0; Counter < XMarks.Length; Counter++)
                                {
                                    Double YCoord = Y1;
                                    while (YCoord <= Y2)
                                    {
                                        YCoord = (YCoord + GetGraphAttribute(DrawType.IntermediateLines).AttributeLength >= Y2) ? Y2 : YCoord + GetGraphAttribute(DrawType.IntermediateLines).AttributeLength;
                                        RealDrawer.DrawLine(IntermediateLinesPen,
                                                            (float)XMarks[Counter], (float)YCoord,
                                                            (float)XMarks[Counter], (float)(YCoord - GetGraphAttribute(DrawType.IntermediateLines).AttributeLength));
                                        YCoord += 2 * GetGraphAttribute(DrawType.IntermediateLines).AttributeLength;
                                    }
                                }

                                for (Int32 Counter = 0; Counter < YMarks.Length; Counter++)
                                {
                                    Double XCoord = X1;
                                    while (XCoord <= X3)
                                    {
                                        XCoord = (XCoord + GetGraphAttribute(DrawType.IntermediateLines).AttributeLength >= X3) ? X3 : XCoord + GetGraphAttribute(DrawType.IntermediateLines).AttributeLength;
                                        RealDrawer.DrawLine(IntermediateLinesPen,
                                                            (float)XCoord, (float)YMarks[Counter],
                                                            (float)(XCoord - GetGraphAttribute(DrawType.IntermediateLines).AttributeLength), (float)YMarks[Counter]);
                                        XCoord += 2 * GetGraphAttribute(DrawType.IntermediateLines).AttributeLength;
                                    }
                                }
                            }
                        }

                        String[] StringXMarks = new String[XMarks.Length];
                        Double TempMark = 0.0;
                        using (SolidBrush AxisBrush = new SolidBrush(GetGraphAttribute(DrawType.XAxisTitle).AttributeColor))
                        {
                            for (Int32 Counter = 0; Counter < XMarks.Length; Counter++)
                            {
                                TempMark = (GetGraphAttribute(DrawType.XScaleFactor).AttributeEnabled == true) ?
                                            (Double)(((XMarks[Counter]
                                            + 1
                                            + DrawOffsetIndex - Points[0].X) / Positioning().GetXScaleCoefficient()) *
                                            GetGraphAttribute(DrawType.XScaleFactor).AttributeMultiplyingFactor) + GetGraphAttribute(DrawType.XScaleFactor).AttributeAddingValue :
                                            (Double)(XMarks[Counter] + DrawOffsetIndex - Points[0].X);
                                StringXMarks[Counter] = (TempMark).ToString();

                                RealDrawer.DrawString(String.Format(GetGraphAttribute(DrawType.XAxisMarks).AttributeName,
                                                                    Convert.ToDecimal(StringXMarks[Counter])),
                                                      GetGraphAttribute(DrawType.XAxisMarks).AttributeFont,
                                                      AxisBrush,
                                                      (float)XMarks[Counter], (float)Y2 + 2);
                            }

                            // Определение показателя для минимального значения ....
                            Int32 Degree = DegreeDefining.NumberExponent(Double.Parse(GraphSource[0].ToString()));
                            String[] StringYMarks = new String[YMarks.Length];
                            for (Int32 Counter = 0; Counter < YMarks.Length; Counter++)
                            {
                                Int32 Searching = FindClosestPointXCoordinate(YMarks[Counter]);
                                StringYMarks[Counter] = (Double.Parse(GraphSource[(Int32)((Double)(Searching - (Int32)Points[0].X) / Positioning().GetXScaleCoefficient())].ToString()) / Math.Pow(10.0, Degree)).ToString();
                                RealDrawer.DrawString(String.Format(GetGraphAttribute(DrawType.YAxisMarks).AttributeName,
                                                                    Convert.ToDecimal(StringYMarks[Counter])),
                                                      GetGraphAttribute(DrawType.YAxisMarks).AttributeFont,
                                                      AxisBrush,
                                                      (float)1.5 * GetGraphAttribute(DrawType.XAxisTitle).AttributeFont.Size,
                                                      (float)YMarks[Counter]);

                            }
                            // Написание степени 10^Degree
                            RealDrawer.DrawString("•10", GetGraphAttribute(DrawType.YAxisMarks).AttributeFont,
                                                  AxisBrush, (float)X1 - 20, (float)Y1 - 20);
                            using (Font DefreeFont = new Font("Times New Roman", 8, FontStyle.Bold))
                            {
                                RealDrawer.DrawString(Degree.ToString(),
                                                      DefreeFont,
                                                      AxisBrush, (float)X1 - 5, (float)Y1 - 30);
                            }
                        }

                    }
                }

                private Int32 FindClosestPointXCoordinate(Double YCoordinate)
                {
                    Double MinimalDelta = Math.Abs((Double)Points[0].Y - YCoordinate);
                    Int32 MinimalDeltaIndex = 0;
                    for (Int32 Counter = 0; Counter < Points.Length; Counter++)
                    {
                        if (Math.Abs((Double)Points[Counter].Y - YCoordinate) < MinimalDelta)
                        {
                            MinimalDelta = Math.Abs((Double)Points[Counter].Y - YCoordinate);
                            MinimalDeltaIndex = Counter;
                        }
                    }
                    return Points[MinimalDeltaIndex].X;
                }

                public void UpdateOffsetIndex(Object Sender, ScrollEventArgs Args)
                {
                    if (Points.Length < Positioning().GetRealWidth()) return;
                    // Получение индекса начала рисования как событие скроллбара
                    SetDrawOffsetIndex = (UInt16)Args.NewValue;
                    ImageContainer.Invalidate();
                }

                public UInt16 SetDrawOffsetIndex
                {
                    get { return DrawOffsetIndex; }
                    set
                    {
                        if (value < (UInt16)Points.Length - (UInt16)Positioning().GetRealWidth()) DrawOffsetIndex = value;
                        else DrawOffsetIndex = (UInt16)(Points.Length - Positioning().GetRealWidth());
                    }
                }

                public void HandleMouseClick(object sender, MouseEventArgs Args)
                // установка положения маркера по клику мыши...
                {
                    if (Points == null || ImageContainer == null) return;
                    if (Args.Button == MouseButtons.Right)
                    {
                        DisableGraphAttribute(LastHandled);
                    }

                    else if (Args.Button == MouseButtons.Left)
                    {
                        EnableGraphAttribute(LastHandled);
                        SetMarkerPosition(LastHandled, Args.X - Points[0].X);
                    }
                    ImageContainer.Invalidate();
                }

                public Int32 GetPointsArrayIndex(Int32 XCoordinate)
                {
                    if (XCoordinate < Points[0].X) return DrawOffsetIndex;
                    else if (XCoordinate >= Points[0].X + Positioning().GetRealWidth()) return (Int32)((Double)(Positioning().GetRealWidth() + DrawOffsetIndex - 1) / Positioning().GetXScaleCoefficient());
                    else return (Int32)((Double)(XCoordinate + DrawOffsetIndex - Points[0].X) / Positioning().GetXScaleCoefficient());
                }

                public void IncrementMarkerPosition(DrawType TypeOfMarker)
                {
                    if (TypeOfMarker == DrawType.LeftMarkers)
                    {
                        LastHandled = DrawType.LeftMarkers;
                        if (LeftMarkerPosition + ((Int32)(1.0 * Positioning().GetXScaleCoefficient())) < Positioning().GetRealWidth())
                            LeftMarkerPosition += (Int32)(1.0 * Positioning().GetXScaleCoefficient());
                        else LeftMarkerPosition = Points[0].X;
                    }

                    else if (TypeOfMarker == DrawType.RightMarkers)
                    {
                        LastHandled = DrawType.RightMarkers;
                        if (RightMarkerPosition + ((Int32)(1.0 * Positioning().GetXScaleCoefficient())) < Positioning().GetRealWidth())
                            RightMarkerPosition += (Int32)(1.0 * Positioning().GetXScaleCoefficient());
                        else RightMarkerPosition = Points[0].X;
                    }
                    if (ImageContainer != null) ImageContainer.Invalidate();
                }

                public void DecrementMarkerPosition(DrawType TypeOfMarker)
                {
                    if (TypeOfMarker == DrawType.LeftMarkers)
                    {
                        LastHandled = DrawType.LeftMarkers;
                        if (LeftMarkerPosition - ((Int32)(1.0 * Positioning().GetXScaleCoefficient())) >= Points[0].X)
                            LeftMarkerPosition -= (Int32)(1.0 * Positioning().GetXScaleCoefficient());
                        else LeftMarkerPosition = (Int32)(Positioning().LeftSpaceLobe * Positioning().GetCanvasContainerWidth() + Positioning().GetRealWidth() - 1);
                    }

                    else if (TypeOfMarker == DrawType.RightMarkers)
                    {
                        LastHandled = DrawType.RightMarkers;
                        if (RightMarkerPosition - ((Int32)(1.0 * Positioning().GetXScaleCoefficient())) >= Points[0].X)
                            RightMarkerPosition -= (Int32)(1.0 * Positioning().GetXScaleCoefficient());
                        else RightMarkerPosition = (Int32)(Positioning().RightSpaceLobe * Positioning().GetCanvasContainerWidth() + Positioning().GetRealWidth());
                    }
                    if (ImageContainer != null) ImageContainer.Invalidate();
                }

                public void SetMarkerPosition(DrawType TypeOfMarker, Int32 NewPosition)
                {
                    if (TypeOfMarker == DrawType.LeftMarkers)
                    {
                        LastHandled = DrawType.LeftMarkers;
                        if (NewPosition < Positioning().GetRealWidth() && NewPosition > 0)
                            LeftMarkerPosition = Points[0].X + (Int32)NewPosition;
                        else
                        {
                            if (NewPosition < 0)
                                LeftMarkerPosition = Points[0].X + Positioning().GetRealWidth() - 1;
                            else if (NewPosition >= Positioning().GetRealWidth())
                                LeftMarkerPosition = Points[0].X;
                        }
                    }

                    else if (TypeOfMarker == DrawType.RightMarkers)
                    {
                        LastHandled = DrawType.RightMarkers;
                        if (NewPosition <= Positioning().GetRealWidth() && NewPosition >= 0)
                            RightMarkerPosition = Points[0].X + (Int32)NewPosition;
                        else
                        {
                            if (NewPosition < 0)
                                RightMarkerPosition = Points[0].X + Positioning().GetRealWidth();
                            else if (NewPosition > Positioning().GetRealWidth())
                                RightMarkerPosition = Points[0].X;
                        }
                    }
                    if (ImageContainer != null) ImageContainer.Invalidate();
                }

                public Int32 GetMarkerPosition(DrawType TypeOfMarker)
                {
                    if (Points != null)
                        return TypeOfMarker == DrawType.LeftMarkers ? LeftMarkerPosition - Points[0].X : RightMarkerPosition - Points[0].X;
                    return 0;
                }

                public Int32 GetArrayIndexAccordingMarkerPosition(DrawType MarkerType)
                {
                    if (Points != null)
                        return MarkerType == DrawType.LeftMarkers ? (LeftMarkerPosition - Points[0].X + DrawOffsetIndex) / (Int32)Positioning().GetXScaleCoefficient() :
                                                                    (RightMarkerPosition - Points[0].X + DrawOffsetIndex) / (Int32)Positioning().GetXScaleCoefficient();
                    else return 0;
                }
            }
        }
    }
}
